package com.suxin.utils;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * @author Tang
 * @classname PropertyMapper
 * @description [  ]
 * @date 2023/4/11 15:08
 */
public class PropertyMapper {

    private static final PropertyMapper INSTANCE = new PropertyMapper();

    public static PropertyMapper get() {
        return INSTANCE;
    }

    /**
     * 获取属性
     * @param supplier
     * @param <T>
     * @return
     */
    public <T> Source<T> from(Supplier<T> supplier) {
        return getSource(supplier);
    }

    /**
     * 获得属性
     * @param supplier
     * @param predicate
     * @param <T>
     * @return
     */
    public <T> Source<T> from(Supplier<T> supplier,Predicate<T> predicate) {
        return getSource(supplier,predicate);
    }

    private <T> Source<T> getSource(Supplier<T> supplier,Predicate<T> predicate) {
        return new Source<>(new CachingSupplier<>(supplier), predicate);
    }

    private <T> Source<T> getSource(Supplier<T> supplier) {
        return getSource(supplier, (t) -> true);
    }

    private static class CachingSupplier<T> implements Supplier<T>{

        private final Supplier<T> supplier;

        public CachingSupplier(Supplier<T> supplier) {
            this.supplier = supplier;
        }

        @Override
        public T get() {
            return this.supplier.get();
        }

    }

    public static final class Source<T>{

        @Getter
        @Setter
        private Supplier<T> supplier;

        @Getter
        @Setter
        private Predicate<T> predicate;

        public Source(Supplier<T> supplier, Predicate<T> predicate) {
            this.supplier = supplier;
            this.predicate = predicate;
        }

        /**
         * 条件计算
         * @param consumer
         */
        public void to(Consumer<T> consumer) {
            final T result = this.getSupplier().get();
            if (this.getPredicate().test(result)) {
                consumer.accept(result);
            }
        }

        /**
         * whenNonNull
         * @return
         */
        public Source<T> whenNonNull() {
            return new Source<>(new NullPointerExceptionSafeSupplier<>(this.getSupplier()), Objects::nonNull);
        }

        /**
         * 条件 and
         * @param predicate
         * @return
         */
        public Source<T> when(Predicate<T> predicate) {
            Predicate<T> predicateAnd = this.getPredicate().and(predicate);
            return new Source<>(new NullPointerExceptionSafeSupplier<>(this.getSupplier()), predicateAnd);
        }

        /**
         * 转换
         * @param function
         * @param <R>
         * @return
         */
        public <R> Source<R> as(Function<T, R> function) {
            final T v = this.getSupplier().get();
            Supplier<R> supplier = () -> function.apply(v);
            Predicate<R> predicate = (t) -> this.getPredicate().test(v);
            return new Source<>(supplier, predicate);
        }

    }

    public static final class NullPointerExceptionSafeSupplier<T> implements Supplier<T> {

        private final Supplier<T> supplier;

        public NullPointerExceptionSafeSupplier(Supplier<T> supplier) {
            this.supplier = supplier;
        }

        @Override
        public T get() {
            try {
                return this.supplier.get();
            } catch (Exception e) {
                return null;
            }
        }
    }

    public static void main(String[] args) {
        final User user1 = new User();
        user1.setUsername("ni");
        user1.setAge(18);
        final User user2 = new User();
        user2.setUsername("hell");
        final PropertyMapper propertyMapper = PropertyMapper.get();
        propertyMapper
                .from(user1::getUsername)
                .whenNonNull()
                .when((item) -> item.length() > 5)
                .to(user2::setUsername);
        System.out.println(user2);
    }

    @Data
    public static class User{
        private String username;
        private Integer age;
    }
}